package jvm规范;

import javax.management.InvalidApplicationException;

import jdk.internal.reflect.Reflection;

public class JVM加载机制 {

}
【JVM类记载机制介绍】
JVM类记载机制就是把描述类的数据从class文件加载到内存，并对数据进行校验、转换解析和初始化，最终形成可以被虚拟机直接使用的Java类型。在Java语言里面，类型的加载、连接和初
始化都是在程序运行期间完成的。分为下面3钟

1. 全盘负责，当一个类加载器负责加载某个Class时，该Class所依赖的和引用的其他Class也将由该类加载器负责载入，除非显示使用另外一个类加载器来载入

2. 父类委托，先让父类加载器试图加载该类，只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类

3. 缓存机制，缓存机制将会保证所有加载过的Class都会被缓存，当程序中需要使用某个Class时，类加载器先从缓存区寻找该Class，只有缓存区不存在，系统才会读取该类对应的二进制数据，
并将其转换成Class对象，存入缓存区。这就是为什么修改了Class后，必须重启JVM，程序的修改才会生效

【JVM类加载时机】
类的整个生命周期包括：加载（loading）、验证（verification）、准备（preparation）、解析（resolution）、初始化（initialization）、使用（using）和
卸载（unloading）7个阶段。其中 验证、准备、解析 3个部分统称连接（linking）。

加载、验证、准备、初始化与卸载这五个阶段的顺序是确定，类的加载过程按上图的顺序按部就班地开始，而解析阶段则不一定JVM规范并没有明确解析的顺序，其由可能发生
在初始化阶段之后。注意：上图表示的是各阶段的开始时间之间的顺序关系，而不是说验证阶段要等待加载阶段完成以后才能进行，实际这些阶段通常都是互相交叉地混合式
进行的，在一个阶段执行的过程中会调用或激活下一个阶段。

5种必须立即初始化的情况：

1.遇到new, getstatic, putstatic 或 invokestatic 这4条字节码指令；

2.使用 java.lang.reflect 包的方法对类进行反射调用；

3.初始化一个类的时候，其父类还没有初始化，必须先触发其父类的初始化；（对于接口，只有在真正用到其父接口的时候才会初始化）

4.虚拟机启动时，用户指定要执行的主类；

5.当使用 JDK 1.7 的动态语言支持时，如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic, REF_putStatic, REF_invokeStatic 的
方法句柄，并且这个方法句柄锁对应的类没有进行初始化，则需要先触发其初始化。对于静态字段，只有直接定义这个字段的类才会被初始化。


被动引用不会：
1.通过子类调用父类的static字段，只会初始化父类，不会初始化子类。
2.new ClassA[10]不会初始化ClassA
3. ClassA中的final static常量，被ClassB引用，但是不会初始化ClassA，因为常量在编译阶段会存入调用类的常量池中，本质上没有直接引用到定义常量的类。

JVM类加载过程:
1.加载
"加载"是“类加载过程”的一个阶段，在初始化之前完成（初始化的时机上文已经说了）。在加载阶段需要完成下面3件事情：

1）通过一个类的全限定名来获取定义此类的二进制字节流
2）将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3）在内存中生成一个代表这个类的java.lang.Class对象，作为方法区这个类的各种数据的访问入口

获取二进制字节流的方式：
▪从ZIP包中获取，最终成为日后JAR/EAR/WAR格式的基础;
▪从网络获取，最典型的应用就是Applet;
▪运行时计算生成，这种场景使用最多的就是动态代理技术;
▪由其他文件生成，典型场景是JSP应用，即由JSP文件生成对应的Class类;
▪从数据库读取，相对少见，有些中间件服务器可以选择把程序安装到数据库中来完成程序代码在集群间的分发;
and so on..

该阶段主要将不同来源的类的字节码文件转化为Class对象，以便访问类的各类信息。延迟加载是:只有真正要使用类时才会触发类的加载。类的加载过程包
括加载、验证、准备、解析、初始化的五个阶段，从Java开发的角度来看，只有加载阶段能被干预，其他阶段都是由JVM主导。

2.验证
验证是连接阶段的第一步，目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求，并且不会危害虚拟机自身的安全。
因为Class文件的来源不一定是编译产生，甚至可以直接用十六进制编辑器直接编写。所以如果不检查输入的字节流，对其完全信任的话，很可能因为载入有害的字节流而导致系统崩溃。
从整体来看，验证阶段大致完成下面4个阶段的检验：
	1）文件格式验证
字节流是否符合Class文件格式的规范，并且能被当前版本的虚拟机处理。比如是否以魔数0xCAFEBABE开头、主次版本号是否在当前虚拟机处理范围内、常量池的常量中是否有不被支持
的常量类型（检查常量tag标志）等等等等。该验证阶段的主要目的是保证输入的字节流能正确地解析并存储于方法区之内，格式上符合描述一个JAVA类型信息的要求。只有通过这个阶段
的验证后，字节流才会进入内存的方法区进行存储，所以后面的3个验证阶段全部是基于方法区的存储结构进行的，不会再直接操作字节流。
	2）元数据验证
这一阶段是对于字节码描述的信息进行语义分析，以保证其描述的信息符合Java语言规范的要求。可能包括的验证点如下：
是否有父类；
父类是否继承了不被允许的类（被final修饰的）；
如果不是抽象类，是否实现了其父类或者接口之中要求实现的所有方法；
类中的字段、方法是否与父类产生矛盾（比如覆盖了父类的final字段或者出现不符合规则的方法重载，例如方法参数都一致但返回值不同等）；
and so on...
	3）字节码验证
进行数据流和控制流分析，即对类的方法体进行校验分析以保证被校验类的方法在运行时不会做出危害虚拟机安全的行为（如：保证跳转指令不会跳转到方法体以外的字节码指令上等）
即使一个方法通过了字节码验证，也不能说明其一定是安全的（通过程序去校验程序逻辑是无法做到绝对准确的）
	4）符号引用验证
对类自身以外（常量池中的各种符号引用）的信息进行匹配性校验，通常检查：符号引用中通过字符串描述的全限定名是否能找到对应的类、指定的类中是否存在符合描述符与简单名称描述
的方法与字段

3.准备
准备阶段是正式为类变量（被static修饰的变量）分配内存并设置类变量初始值（通常情况下是数据类型的0值，具体赋值阶段是初始化阶段）的阶段，这些变量所使用的内存都将在方法区
中进行分配。如果类字段的字段属性表中包含ConstantValue属性，那在准备阶段变量就会被初始化为ConstantValue属性所指定的值，即如果a变量定义变
为public final static int a = 123;，编译时javac会为a生成ConstantValue属性，准备阶段虚拟机就会根据ConstantValue的设置将a的值置为123 。

准备阶段是正式为类变量（被static修饰的变量）分配内存并设置零值的阶段，其内存都分配在方法区，不同类型的变量都会有对应的零值比如引用类型
是null、int 是0 、boolean是false等。注意：这个阶段只会为类变量分配内存，而实例变量内存的分配要随着对象实例化时一起分配在堆内存中; 同时如果类变量是一个静态常
量（同时被static与final修饰的变量），这阶段就会为其赋值成对应的常量值。
public static int a = 9;
public static final int b = 613;
public static String c = "yeyi";
上面的例子在这个阶段 a 的值为0，b值为613，c值为 null。

4.解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
1)符号引用：以一组符号来描述所引用的目标，符号可以是任何形式的字面量，只要使用时可以无歧义的定位到目标即可。符号引用与虚拟机实现的内存布局无关，引用目标并不一定已经加
载到内存中
2)直接引用：直接指向目标的指针、相对偏移量或一个能间接定位到目标的句柄，直接引用与虚拟机实现的内存布局相关，如果有了直接引用，引用目标必定已经加载到内存中虚拟机规范并
未规定解析动作发生的具体时间，仅要求在执行anewarray、checkcast、getfield、getstatic、instanceof、invokeinterface、invokespecial、
invokestatic、invokevirtual、multianewarray、new、putfield和putstatic这13个用于操作符号引用的字节码指令之前，先对它们所使用的符号引用进行解析。

下面说的很清楚：
在java中，一个java类将会编译成一个class文件。在编译时，java类并不知道引用类的实际内存地址，因此只能使用符号引用来代替。比如org.simple.People类引
用org.simple.Tool类，在编译时People类并不知道Tool类的实际内存地址，因此只能使用符号org.simple.Tool(假设)来表示Tool类的地址。而在类装载器装
载People类时，此时可以通过虚拟机获取Tool类 的实际内存地址，因此便可以既将符号org.simple.Tool替换为Tool类的实际内存地址，及直接引用地址。

解析阶段实际是将符号引用解析成为直接引用，符号引用只是一个区分的标识并不指向对象真正存储的内存地址，而直接引用可以直接指向目标的指针、相对偏移量或能定位到目标的句
柄。比如Person类中引用了一个 Address 类，一开始 Person 类并不知道 Address 类在内存中的地址，所以就先搞个符号引用替代一下，假装知道，等类加载解析的时候再将
这个符号引用替换为Address 类真正的内存地址。

5.初始化
类初始化阶段是“类加载过程”中最后一步，在之前的阶段，除了加载阶段用户应用程序可以通过自定义类加载器参与，其它阶段完全由虚拟机主导和控制，直到初始化阶段，才真正开始
执行类中定义的Java程序代码

初始化是类加载的最后一步，初始化阶段会为类变量赋予初始值（不包括静态常量，即：同时被static与final修饰的变量）。其实际是执行类的初始化方法clinit<>，该方法会被加锁
执行，且只执行一次。clinit<>方法会按程序定义的顺序收集类中的static代码块将其放入clinit<>方法中。clinit<>方法与对象的构造函数init<>不同，其不能显示调用父类
的clinit<>方法，JVM会保证调用子类的clinit<>方法前，父类的clinit<>方法已被调用。JVM 规范 Java SE 8 版本第五节中明确规定只有6种情况会触发类的初始化，同时
在 Java语言规范 Initialization of Classes and Interfaces一节中有相对于Java工程师更直观的说明。下面是JVM规范 Java SE 8 版本明确的会触发类的初始化
的6种情况。
1）JVM启动时用户要指定一个主函数（main方法），JVM会先初始化该方法所属的类。
2）执行new、getstatic、putstatic、invokestatic节码指令时引用了对应类且类未初始化，则先初始化该类。这4条指令常见的Java代码场景是：使用new关键字实例化对象、读
   取或设置一个类的静态变量（被final修饰的静态变量除外，其值在常用池中）、调用一个类的静态方法。
3）通过java.lang.reflect包或Class类中方法对类进行反射操作时，如果类未初始化，则会先初始化该类。
4）当初次调用MethodHandle 实例时，初始化该MethodHandle指向的方法所在类。
5）当一个类的其子类在初始化时，如果该类未初始化则会触发其初始化。
6）当一个接口定义了default方法，其直接或间接实现类在初始时，若该接口未初始化则会触发其初始化。
上面的这6种情况也被叫作主动使用，其他的都是被动使用，即使代码中引用了对应的class，JVM也不会对类进行初始化。

可以用下面方式验证接口与类是否初始化。
public interface A {
	//验证接口是否初始初始化
	int a = Test.out("A", 1);
}
public class Test {
	public static int out(String i, int v) {
		System.out.println("Interface" + i + "init!");
		return v;
	}
}
public class B {
	static {
		//验证类是否初始初始化
		System.out.println("Class B init!");
	}
}
注意：验证类是否加载与类是否初始化不要混淆，一个类可能已加载但没有初始化，类是否加载通过运行时加-verbose:class能看到，类是已初始化可以通过上面的代码看输出。


【JVM类加载器】
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现，以便让应用程序自己决定如何去获取所需要的类。
实现这个动作的代码模块称为类加载器。

对于任意一个类，都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性，每一个类加载器都拥有一个独立的类名称空间。即比较两个类是否“相等”，只有在这
两个类是由同一个类加载器加载的前提下才有意义。“相等”包括代表类的Class对象的equals()方法、isAssinableFrom()方法、isInstance()方法的返回结果，也包括
使用instanceof关键字做对象所属关系判定等情况。

【双亲委派模型】
ClassLoader加载资源与类时遵循代理模式，每一个ClassLoader有对应的父类加载器。当ClassLoader要加载一个类时，其会先尝试让他的父类加载器去加载该类，只有在
父类加载器无法加载该类时其自己才会尝试去加载该类，而父类加载器又会按照这个规则优先让他的父类加载器去加载类，直到最上层的引导类加载器，这种JVM加载类的默认规则通
常被叫作双亲委派模型。

从Java虚拟机角度来讲，只存在两种不同的类加载器：一种是启动类加载器，是虚拟机自身的一部分；另一种就是所有其他的类加载器，独立于虚拟机外部，全都继承自抽象
类java.lang.ClassLoader。

从Java开发人员的角度来看，类加载器分为3种： 

启动类加载器(Bootstrap ClassLoader):这个类存放在<JAVA_HOME>\lib目录中，无法被Java程序直接引用，用户在编写自定义类加载器时，如果需要把加载请求委派给引
导类加载器，那直接使用null代替即可。
拓展类加载器(Extension ClassLoader):这个类加载器负责加载<JAVA_HOME>\lib\ext目录中的所有类库，开发者可以直接使用。

引导类加载器（Bootstrap ClassLoader）、
扩展类加载器（Extension ClassLoader）
应用类加载器（App ClassLoader，也叫系统类加载器System ClassLoader。

▪引导类加载器主要负责加载 JDK中的核心类，这些类位于 <JAVA_HOME>/lib/rt.jar 文件中，日常开发常用的JDK类都在java.xxx.* 里面，比
如 java.util.、java.io.、java.nio.、java.lang. 等等。引导类加载器比较特殊，它是由c语言实现的，他也被叫作根加载器。

▪扩展类加载器主要负责加载 JDK中的扩展类，这些类通常位于 <JAVA_HOME>/lib/ext/*.jar 文件中，但可以通过设置系统运行参数 -Djava.ext.dirs=你要指定的扩展类
目录 进行修改，不过最好别这么做。
*/
▪应用类加载器主要负责加载工程中自己开发的类以及依赖第三方类库，它加载在环境变量 CLASSPATH、-classpath 或 -cp 命令行选项中找到的应用程序类型类。可以通
过ClassLoader 类提供的静态方法 getSystemClassLoader() 获取应用类加载器。当启动一个main函数时，应用类加载器开始加载main函数所属的类。

JDK中还内置了URLClassLoader用于从指定文件目录或是从网络中加载字节码文件，扩展类加载器与应用类加载器都是他的子类。扩展类加载器与应用类加载器
都sun.misc.Luancher的内部类，分别对应ExtClassLoader类与AppClassLoader类。

注意：父类加载器不能访问子类加载器加载的类，而子类加载却能访问父类加载器加载的类。上面说的父类加载器与子类加载器，不要与类继承关系上的父子类的概念混
淆，AppClassLoader 的父类加载器是ExtClassLoader，但在的类继承上他们之间不存在父子关系，他们都是URLClassLoader的直接子类。

一、类加载
类加载分为三个步骤：加载、链接、初始化。

1.加载
●类加载指的是将class文件读入内存，并为之创建一个java.lang.Class对象，即程序中使用任何类时，系统都会为之建立一个java.lang.Class对象，系统中所有的类
 都是java.lang.Class的实例。
●类的加载由类加载器完成，JVM提供的类加载器叫做系统类加载器，此外还可以通过继承ClassLoader基类来自定义类加载器。
●通常可以用如下几种方式加载类的二进制数据：
	1)从本地文件系统加载class文件。
	2)从JAR包中加载class文件，如JAR包的数据库启驱动类。
	3)通过网络加载class文件。
	4)把一个Java源文件动态编译并执行加载。

2.链接
连接阶段负责把类的二进制数据合并到JRE中，其又可分为如下三个阶段：
	1)验证：确保加载的类信息符合JVM规范，无安全方面的问题。
	2)准备：为类的静态Field分配内存，并设置初始值。
	3)解析：将类的二进制数据中的符号引用替换成直接引用。

3.初始化
●该阶段主要是对静态Field进行初始化，在Java类中对静态Field指定初始值有两种方式：
	1)声明时即指定初始值，如static int a = 5；
	2)使用静态代码块为静态Field指定初始值，如：static{    b = 5;    } 

●JVM初始化一个类包含如下几个步骤：
	1)假如这个类还没有被加载和连接，则程序先加载并连接该类。
	2)假如该类的直接父类还没有被初始化，则先初始化其直接父类。
	3)假如类中有初始化语句，则系统依次执行这些初始化语句。
	
	所以JVM总是最先初始化java.lang.Object类。

●类初始化的时机（对类进行主动引用时）：
	1)创建类的实例时（new、反射、反序列化）。
	2)调用某个类的静态方法时。
	3)使用某个类或接口的静态Field或对该Field赋值时。
     当一个接口定义了default方法，其直接或间接实现类在初始时，若该接口未初始化则会触发其初始化
	4)使用反射来强制创建某个类或接口对应的java.lang.Class对象，如Class.forName("Person")
    通过java.lang.reflect包或Class类中方法对类进行反射操作时，如果类未初始化，则会先初始化该类。
	5)初始化某个类的子类时，此时该子类的所有父类都会被初始化。
	6)直接使用java.exe运行某个主类时。JVM启动时用户要指定一个主函数（main方法），JVM会先初始化该方法所属的类。

   7)当初次调用MethodHandle 实例时，初始化该MethodHandle指向的方法所在类。

二、类加载器及加载机制
java.lang.Object
  └─java.lang.ClassLoader

类加载器负责将class文件读入内存，并为之生成对应的java.lang.Class对象。

1.Java9之前
●类加载器
当JVM启动时，会形成有3个类加载器组成的初始类加载器层次结构：

(1).Bootstrap ClassLoader：根类（或叫启动、引导类加载器）加载器。

它负责加载Java的核心类（如String、System等）。它比较特殊，因为它是由原生C++代码实现的，并不是java.lang.ClassLoader的子类，所以下面的运行结果为null：

public class TestJdkCl {
	public static void main(String[] args) {
		System.out.println(String.class.getClassLoader());
	}
}

(2).Extension ClassLoader：扩展类加载器。

它负责加载JRE的扩展目录（%JAVA_HOME%/jre/lib/ext）中JAR包的类，我们可以通过把自己开发的类打包成JAR文件放入扩展目录来为Java扩展核心类以外的新功能。

(3).System ClassLoader（或Application ClassLoader）：系统类加载器。

它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性，或CLASSPATH环境变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader来获取系统类加载器：

public class TestJdkCl {
//获取主类的类加载器
	public static void main(String[] args) {
		System.out.println(TestJdkCl.class.getClassLoader().getClass().getName());
		System.out.println(ClassLoader.getSystemClassLoader().getClass().getName());
	}
}
运行结果：
log4j2Test: jdk.internal.loader.ClassLoaders$AppClassLoader
log4j2Test: jdk.internal.loader.ClassLoaders$AppClassLoader

●类加载机制
                Bootstrap ClassLoader  根类加载器
                              ↑
                Extension ClassLoader  扩展类加载器
                              ↑
                System ClassLoader  系统(应用)类加载器
                   ↗         ↖
       User ClassLoader  User ClassLoader 
        用户自定义类加载器    用户自定义类加载器

JVM的类加载机制主要有以下3种：

◆全盘负责：当一个类加载器加载某个Class时，该Class所依赖和引用的其它Class也将由该类加载器负责载入，除非显式的使用另外一个类加载器来载入。
◆双亲委派：当一个类加载器收到了类加载请求，它会把这个请求委派给父（parent）类加载器去完成，依次递归，因此所有的加载请求最终都被传送到顶层的启动类加载器中。只有
         在父类加载器无法加载该类时子类才尝试从自己类的路径中加载该类。（注意：类加载器中的父子关系并不是类继承上的父子关系，而是类加载器实例之间的关系。）
◆缓存机制：缓存机制会保证所有加载过的Class都会被缓存，当程序中需要使用某个类时，类加载器先从缓冲区中搜寻该类，若搜寻不到将读取该类的二进制数据，并转换
         成Class对象存入缓冲区中。这就是为什么修改了Class后需重启JVM才能生效的原因。

2.Java9的改变
●类加载器
JDK 9保持三级分层类加载器架构以实现向后兼容。但是，从模块系统加载类的方式有一些变化。且新增Platform ClassLoader：平台类加载器，用于加载一些平台相关的模
块，例如： java.activation 、 java.se 、 jdk.desktop 、 java.compiler 等，双亲是BootClassLoader。 JDK 9类加载器层次结构如下图所示。

                Bootstrap ClassLoader ←──┐
                         ↑               │
                Plateform ClassLoader    │  #平台类加载器可以委托给引导类加载器和应用程序类加载器
                    ↑         ↓          │
                Application ClassLoader ─┘  #应用程序类加载器可以委托给平台类加载器以及引导类加载器
                       ↗         ↖
            User ClassLoader  User ClassLoader 
             用户自定义类加载器    用户自定义类加载器

     在委派给父加载器加载前，要先判断该类是否能够归属到某一个系统模块中，如果可以找到这样的归属关系，就要优先委派给负责那个模块的加载器完成加载。

可见，在JDK 9中，应用程序类加载器可以委托给平台类加载器以及引导类加载器；平台类加载器可以委托给引导类加载器和应用程序类加载器。 

此外，JDK 9不再支持扩展机制。 但是，它将扩展类加载器保留在名为平台类加载器的新名称下。 ClassLoader类包含一个名为getPlatformClassLoader()的静态
方法，该方法返回对平台类加载器的引用。
public abstract class ClassLoader {
	...

	public static ClassLoader getPlatformClassLoader() {
		@SuppressWarnings("removal")
		SecurityManager sm = System.getSecurityManager();
		ClassLoader loader = getBuiltinPlatformClassLoader();
		if (sm != null) {
			checkClassLoaderPermission(loader, Reflection.getCallerClass());
		}
		return loader;
	}
	...
}

在JDK 9之前，扩展类加载器和应用程序类加载器都是java.net.URLClassLoader类的一个实例。 而在JDK 9中，平台类加载器（以前的扩展类加载器）和应用程序类加载器
是内部JDK类的实例。 如果你的代码依赖于URLClassLoader类的特定方法，代码可能会在JDK 9中崩溃。
现在启动类加载器、平台类加载器、应用程序类加载器全都继承于 jdk.internal.loader.BuiltinClassLoader。？？

public class TestJdkCl {
	public static void main(String[] args) {
		ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();//应用程序类加载器
		System.out.println(appClassLoader.getClass().getName());
		ClassLoader plaClassLoader = appClassLoader.getParent();//应用程序类加载器的父类
		System.out.println(plaClassLoader.getClass().getName());
		System.out.println(plaClassLoader.getParent());//平台类加载器的父类
	}
}
运行结果：
jdk.internal.loader.ClassLoaders$AppClassLoader
jdk.internal.loader.ClassLoaders$PlatformClassLoader
null

●类加载机制
JDK 9中的类加载机制有所改变。 三个内置的类加载器一起协作来加载类。 

1) 当应用程序类加载器需要加载类时，它将搜索定义到所有类加载器的模块。如果有合适的模块定义在这些类加载器中，则该类加载器将加载类，这意味着应用程序类加载器现在
   可以委托给引导类加载器和平台类加载器。 如果在为这些类加载器定义的命名模块中找不到类，则应用程序类加载器将委托给其父类，即平台类加载器。 如果类尚未加载，则
   应用程序类加载器将搜索类路径。 如果它在类路径中找到类，它将作为其未命名模块的成员加载该类。 如果在类路径中找不到类，则抛出ClassNotFoundException异常。        
2) 当平台类加载器需要加载类时，它将搜索定义到所有类加载器的模块。 如果一个合适的模块被定义为这些类加载器中，则该类加载器加载该类。 这意味着平台类加载器可以委
   托给引导类加载器以及应用程序类加载器。 如果在为这些类加载器定义的命名模块中找不到一个类，那么平台类加载器将委托给它的父类，即引导类加载器。
3) 当引导类加载器需要加载一个类时，它会搜索自己的命名模块列表。 如果找不到类，它将通过命令行选项-Xbootclasspath/a指定的文件和目录列表进行搜索。 如果它在
   引导类路径上找到一个类，它将作为其未命名模块的成员加载该类。

   当平台及应用程序类加载器收到类加载请求，在委派给父加载器加载前，要先判断该类是否能够归属到某一个系统模块中，如果可以找到这样的归属关系，就要优先委派给负责
   那个模块的加载器完成加载。


JDK 9 为了模块化的支持，对双亲委派模式做了一些改动：

1)扩展类加载器被平台类加载器（Platform ClassLoader）取代。
JDK 9 时基于模块化进行构建（原来的 rt.jar 和 tools.jar 被拆分成数十个 JMOD 文件），
其中的 Java 类库就已天然地满足了可扩展的需求，那自然无须再保留 <JAVA_HOME>\lib\ext 目录，此前使用这个目录或者 java.ext.dirs 系统变量来扩
展 JDK 功能的机制已经没有继续存在的价值了。

2)平台类加载器和应用程序类加载器都不再继承自 java.net.URLClassLoader。
现在启动类加载器、平台类加载器、应用程序类加载器全都继承于 jdk.internal.loader.BuiltinClassLoader。

如果有程序直接依赖了这种继承关系，或者依赖了 URLClassLoader 类的特定方法，那代码很可能会在 JDK 9 及更高版本的 JDK 中崩溃。

3)启动类加载器现在是在 Java 虚拟机内部和 Java 类库共同协作实现的类加载器（以前是 C++实现）。
为了与之前的代码保持兼容，所有在获取启动类加载器的场景（譬如 Object.class.getClassLoader）中仍然会返回 null 来代替，而不会得
到 BootClassLoader 的实例。

4)类加载的委派关系也发生了变动。
当平台及应用程序类加载器收到类加载请求，在委派给父加载器加载前，要先判断该类是否能够归属到某一个系统模块中，如果可以找到这样的归属关系，就要优先委派给负责
那个模块的加载器完成加载。


【类的初始化步骤 / JVM初始化步骤：】
   1)如果这个类还没有被加载和链接，那先进行加载和链接
   2)假如这个类存在直接父类，并且这个类还没有被初始化（注意：在一个类加载器中，类只能初始化一次），那就初始化直接的父类（不适用于接口）
   3)假如类中存在初始化语句（如static变量和static块），那就依次执行这些初始化语句。

【加载类的方式有以下几种：】
	1）从本地系统直接加载
	2）通过网络下载.class文件
	3）从zip，jar等归档文件中加载.class文件
	4）从专有数据库中提取.class文件
	5）将Java源文件动态编译为.class文件（服务器）
	6）命令行启动应用时候由JVM初始化加载
	7）通过Class.forName()方法动态加载
	8）通过ClassLoader.loadClass()方法动态加载

JDK 9 时基于模块化进行构建（原来的 rt.jar 和 tools.jar 被拆分成数十个 JMOD 文件）	
在 Java 模块化系统明确规定了三个类加载器负责各自加载的模块：

●启动类加载器负责加载的模块
java.base                        java.security.sasl
java.datatransfer                java.xml
java.desktop                     jdk.httpserver
java.instrument                  jdk.internal.vm.ci
java.logging                     jdk.management
java.management                  jdk.management.agent
java.management.rmi              jdk.naming.rmi
java.naming                      jdk.net
java.prefs                       jdk.sctp
java.rmi                         jdk.unsupported

●平台类加载器负责加载的模块
java.activation*                jdk.accessibility
java.compiler*                  jdk.charsets
java.corba*                     jdk.crypto.cryptoki
java.scripting                  jdk.crypto.ec
java.se                         jdk.dynalink
java.se.ee                      jdk.incubator.httpclient
java.security.jgss              jdk.internal.vm.compiler*
java.smartcardio                jdk.jsobject
java.sql                        jdk.localedata
java.sql.rowset                 jdk.naming.dns
java.transaction*               jdk.scripting.nashorn
java.xml.bind*                  jdk.security.auth
java.xml.crypto                 jdk.security.jgss
java.xml.ws*                    jdk.xml.dom
java.xml.ws.annotation*         jdk.zipfs

●应用程序类加载器负责加载的模块
jdk.aot                         jdk.jdeps
jdk.attach                      jdk.jdi
jdk.compiler                    jdk.jdwp.agent
jdk.editpad                     jdk.jlink
jdk.hotspot.agent               jdk.jshell
jdk.internal.ed                 jdk.jstatd
jdk.internal.jvmstat            jdk.pack
jdk.internal.le                 jdk.policytool
jdk.internal.opt                jdk.rmic
jdk.jartool                     jdk.scripting.nashorn.shell
jdk.javadoc                     jdk.xml.bind*
jdk.jcmd                        jdk.xml.ws*
jdk.jconsole

【自定义类加载器】
通常可以通过继承ClassLoader重写findClass方法来实现自定义的类加载器，这样可以保证类加载流程遵循双亲委派模型，但些情况下也可以重写loadClass方法。双亲委派模
型是在JDK1.2时引入的，在这之前loadClass方法已存在，为了向后兼容引入了findClass方法。重写loadCloass会打破双亲委派模型，关于打破双亲委派机制将在后面介绍。
前面了解到ClassLoader中findClass方法默认抛出ClassNotFoundException异常。重写findClass方法实现自定义ClassLoader，一般分两步：

在findClass内部调用私有的loadClassData方法,根据给定名字获取符合JVM规范的字节码数组将获取到的字节码通过CloassLoader#defineClass方法生成对应的Class实例
class CustomClassLoader extends ClassLoader {
  public Class findClass(String name) {
    //1、获取符合JVM规范的字节码数组
    byte[] b = loadClassData(name);
    //2、调用defineClass方法生成Class实例
    return defineClass(name, b, 0, b.length);
  }
  private byte[] loadClassData(String name) {
    //TODO load the class data from somewhere
  }
}

自定义类加载器
ClassLoader 里面有三个重要的方法 loadClass()、findClass() 和 defineClass()。
loadClass() 方法是加载目标类的入口，它首先会查找当前 ClassLoader 以及它的双亲里面是否已经加载了目标类，如果没有找到就会让双亲尝试加载，如果双亲都加载
不了，就会调用 findClass() 让自定义加载器自己来加载目标类。ClassLoader 的 findClass() 方法是需要子类来覆盖的，不同的加载器将使用不同的逻辑来获取
目标类的字节码。拿到这个字节码之后再调用 defineClass() 方法将字节码转换成 Class 对象。

继承ClassLoader类，重写里面的findClass方法。另写一个方法获取.Class/Xlass文件中的二进制数，以数组方式存储返回。
调用父类的defineClass方法，返回一个类。
注：此处的xlass文件是取反加密后的文件。所以在获取数组的时候，有一个遍历取反。

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
//继承ClassLoader类，重写里面的findClass方法。另写一个方法获取.Class/Xlass文件中的二进制数，以数组方式存储返回
public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
            Class log =null;
            //获取该文件的字节码数组
            byte [] classData =getData();

            if (classData !=null){
                log=defineClass(name,classData,0,classData.length);
            }
            return  log;

    }
    public  byte [] getData()  {
        FileInputStream inputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
      	  //此处的xlass文件是取反加密后的文件。所以在获取数组的时候，有一个遍历取反
            inputStream = new FileInputStream("C:\\Users\\DELL\\Desktop\\商城项目\\untitled\\src\\Hello.xlass");
             byteArrayOutputStream=new ByteArrayOutputStream();
            byte [] data =new byte[1024];
            byte [] data2 =new byte[1024];
            int size;
            while ((size =inputStream.read(data))!=-1){
                for (int i = 0; i <size ; i++) {
               	 //此处的xlass文件是取反加密后的文件。所以在获取数组的时候，有一个遍历取反
                    data[i]=(byte) (~data[i]);
                }
                byteArrayOutputStream.write(data,0,size);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return byteArrayOutputStream.toByteArray();
    }

}

利用反射机制，获取类和方法。
jdk9之后废弃了newInstance方法，故采用Class.forName（“name”，Boolean，ClassLoader）的方法获取类对象并初始化
但是此处的newInstance方法被废除，在注释中找到其替代方法 Hello.getDeclaredConstructor().newInstance()。


import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader =new MyClassLoader();
        myClassLoader.findClass("Hello");
        Class<?> Log =myClassLoader.loadClass("Hello");
        Method hello =Log.getDeclaredMethod("hello",null);
        Class<?> Hello = Class.forName("Hello",true,myClassLoader);//true:如果类未初始化，则进行类载入初始化
        //hello.invoke(Hello.newInstance());//在jdk中newInstance被废除，可以在其注释中找到替代方案
        hello.invoke(Hello.getDeclaredConstructor().newInstance());
    }
}


D:\Java\jdk-9\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1.2\lib\idea_rt.jar=49683:C:\Program Files\JetBrains\IntelliJ IDEA 2020.1.2\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\DELL\Desktop\商城项目\untitled\out\production\untitled Test
Hello, classLoader!

Process finished with exit code 0

   

  